SPB Git forge
3commits 1branches 0releases
417.0 KBsize
maindefault branch
10 days agolast push
TypeScript 66.5% Python 30.9% JavaScript 1.4% CSS 0.7%
4.5 KB · 81 lines tsx
Raw Blame History
1import { ImageResponse } from 'next/og';2import { api, safe } from '@/lib/api';3import { fmtDate, fmtInt } from '@/lib/format';4import { OBJECT_TYPE_LABELS, ORBIT_CLASS_COLORS, STATUS_COLORS } from '@/lib/site';56export const size = { width: 1200, height: 630 };7export const contentType = 'image/png';8export const alt = 'SatelliteIndex — satellite orbit card';910// OG renderer cannot resolve CSS variables: mirror the hex tokens from globals.css.11const HEX: Record<string, string> = {12  'var(--active)': '#38d17f',13  'var(--inactive)': '#7c869e',14  'var(--ink-3)': '#6b7694',15  'var(--warn)': '#f5a524',16  'var(--danger)': '#ff5c6c',17  'var(--accent-2)': '#8f7dff',18  'var(--leo)': '#38d3ff',19  'var(--meo)': '#8f7dff',20  'var(--geo)': '#f5b544',21  'var(--heo)': '#ff7ab6',22  'var(--other)': '#7c869e',23};24const hex = (v: string | undefined, fallback: string) => (v ? HEX[v] ?? fallback : fallback);2526export default async function Image({ params }: { params: Promise<{ slug: string }> }) {27  const { slug } = await params;28  const res = await safe(api.satellite(slug));29  const d = res?.data ?? null;30  const statusColor = hex(STATUS_COLORS[d?.status ?? 'UNKNOWN'], '#7c869e');31  const orbitColor = hex(ORBIT_CLASS_COLORS[d?.orbit_class ?? 'UNKNOWN'], '#7c869e');32  const live = d?.live && d.live.error == null ? d.live : null;33  const alt = live ? `${fmtInt(live.altitude_km)} km` : d?.orbital_state ? `${fmtInt(d.orbital_state.perigee_km)}–${fmtInt(d.orbital_state.apogee_km)} km` : '—';3435  return new ImageResponse(36    (37      <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between', padding: 64, background: 'linear-gradient(135deg, #0b1020 0%, #060912 70%)', color: '#eaf0ff', fontFamily: 'sans-serif' }}>38        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>39          <div style={{ width: 18, height: 18, borderRadius: 999, background: '#38d3ff', boxShadow: '0 0 0 6px rgba(56,211,255,0.18)' }} />40          <div style={{ fontSize: 26, letterSpacing: 4, textTransform: 'uppercase', color: '#a3aec8', fontWeight: 600 }}>SatelliteIndex</div>41          {d && (42            <div style={{ marginLeft: 'auto', fontSize: 24, color: '#6b7694', letterSpacing: 2 }}>{`NORAD ${d.norad_id ?? '—'}${d.cospar_id ? ` · ${d.cospar_id}` : ''}`}</div>43          )}44        </div>4546        {d ? (47          <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>48            <div style={{ fontSize: d.name.length > 22 ? 64 : 88, fontWeight: 700, letterSpacing: -3, lineHeight: 1 }}>{d.name}</div>49            <div style={{ display: 'flex', gap: 14, alignItems: 'center' }}>50              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 18px', borderRadius: 999, border: `2px solid ${statusColor}`, color: statusColor, fontSize: 26 }}>51                <div style={{ width: 12, height: 12, borderRadius: 999, background: statusColor }} />52                {d.status.charAt(0) + d.status.slice(1).toLowerCase()}53              </div>54              <div style={{ padding: '8px 18px', borderRadius: 8, background: 'rgba(160,180,230,0.10)', color: '#a3aec8', fontSize: 26 }}>{OBJECT_TYPE_LABELS[d.object_type] ?? d.object_type}</div>55              {d.orbit_class && <div style={{ padding: '8px 18px', borderRadius: 8, background: 'rgba(160,180,230,0.10)', color: orbitColor, fontSize: 26, fontWeight: 700 }}>{d.orbit_class}</div>}56              {d.operator_name && <div style={{ fontSize: 26, color: '#a3aec8' }}>{`· ${d.operator_name}`}</div>}57            </div>58          </div>59        ) : (60          <div style={{ fontSize: 72, fontWeight: 700 }}>Object not catalogued</div>61        )}6263        <div style={{ display: 'flex', gap: 56, borderTop: '1px solid rgba(160,180,230,0.2)', paddingTop: 28 }}>64          {[65            ['Altitude', alt],66            ['Inclination', d?.orbital_state ? `${d.orbital_state.inclination.toFixed(2)}°` : d?.inclination_deg != null ? `${Number(d.inclination_deg).toFixed(2)}°` : '—'],67            ['Period', d?.orbital_state?.period_minutes ? `${d.orbital_state.period_minutes.toFixed(1)} min` : '—'],68            ['Launched', fmtDate(d?.launch_date)],69          ].map(([k, v]) => (70            <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>71              <div style={{ fontSize: 20, letterSpacing: 3, textTransform: 'uppercase', color: '#6b7694' }}>{k}</div>72              <div style={{ fontSize: 40, fontWeight: 600, color: '#eaf0ff' }}>{v}</div>73            </div>74          ))}75        </div>76      </div>77    ),78    { ...size },79  );80}81